using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;

using libsecondlife;
using libsecondlife.InventorySystem;

using Feathertail;

namespace GliderCLI
{
    class GliderCLIApp
    {
        /// <summary>
        /// Main libsl interface
        /// </summary>
        private FeathertailClient _Client;

        private Dictionary<string, Command> CommandDictionary = new Dictionary<string, Command>();
        private List<Command> CommandList = new List<Command>();
        private byte CommandMaxLen = 4;

        /// <summary>
        /// Used to indicate if we're currently connected
        /// </summary>
        private ManualResetEvent _ConnectedSignal = new ManualResetEvent(false);

        /// <summary>
        /// Duh
        /// </summary>
        /// <param IncomingName="args"></param>
        static void Main(string[] args)
        {

            if (args.Length < 3)
            {
                Console.Write("First Name: ");
                String first = Console.ReadLine();

                Console.Write("Last Name: ");
                String last = Console.ReadLine();

                Console.Write("Password: ");
                String pass = Console.ReadLine();

                args = new string[] { first, last, pass };
            }

            GliderCLIApp glider = new GliderCLIApp();

            // Load commands
            List<string> LoadErrors = new List<string>();

            // Load default commands shipped with glider.
            LoadErrors.AddRange(glider.LoadCommands("NestBox.dll", Console.Out));

            // If we had any errors, display them and ask to continue
            if (LoadErrors.Count > 0)
            {
                Console.WriteLine("The following errors occured while loading commands: ");
                foreach (string Error in LoadErrors)
                {
                    Console.WriteLine(Error);
                }

                Console.Write("Do you wish to continue? Y/[N]: ");
                if (!Console.ReadLine().ToLower().Equals("y"))
                {
                    Console.WriteLine("Exiting...");
                    return;
                }
            }

            if (glider.Connect(args[0], args[1], args[2]))
            {
                if (glider._ConnectedSignal.WaitOne(TimeSpan.FromMinutes(1), false))
                {
                    glider.CommandParser();
                    glider.Disconnect();
                }
                else
                {
                    Console.WriteLine("Timed out while waiting to login.");
                }
            }
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        protected GliderCLIApp()
        {
            try
            {
                _Client = new FeathertailClient();
                _Client.Network.OnConnected += new NetworkManager.ConnectedCallback(Network_OnConnected);

                // TODO: Load these kinds of settings from a configuration file of some sort
                _Client.Inventory.LogDescendantQueue = false;
                
                // TODO: Make it so that these sorts of add-ons can be dynamically loaded
                IncomingSort iSort = new IncomingSort(_Client);
                _Client.Inventory.OnRequestDownloadFinishedEvent += new InventoryManager.On_RequestDownloadContents_Finished(Inventory_OnRequestDownloadFinishedEvent);
            }
            catch (Exception e)
            {
                // Error initializing the client
                Console.WriteLine();
                Console.WriteLine(e.ToString());
            }

        }

        void Inventory_OnRequestDownloadFinishedEvent(object iFolder, EventArgs e)
        {
            InventoryFolder theFolder = (InventoryFolder)iFolder;
            Console.WriteLine(theFolder.Name);
        }

        /// <summary>
        /// Get input from CLI, parse and attempt to execute commands.
        /// </summary>
        protected void CommandParser()
        {
            string HorzLine = "-------------------------------------------";

            Console.WriteLine("==================================================================");
            Console.WriteLine("The GliderCLI program provides a simple shell for working with your Second Life[tm] Avatar.");
            Console.WriteLine();
            Console.WriteLine("Type HELP for a list of available commands.");
            Console.WriteLine(HorzLine);

            bool shutdown = false;

            do
            {
                // Display Prompt
                Console.WriteLine();
                Console.Write( "> ");

                // Read input
                string curCmd = Console.ReadLine();
                if (curCmd.Equals(""))
                {
                    continue;
                }

                // Breakup command line, do some fancy quotes processing to group quoted stuff
                string[] curCmdLineParts = curCmd.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                List<string> merged = new List<string>();

                bool inQuotedString = false;
                string temp = "";
                foreach (string s in curCmdLineParts)
                {
                    if (s.StartsWith("\""))
                    {
                        temp = s.Remove(0,1);
                        inQuotedString = true;
                    }
                    else if (s.EndsWith("\""))
                    {
                        temp += " " + s.Remove(s.LastIndexOf('"'));
                        merged.Add(temp);
                        temp = "";
                        inQuotedString = false;
                    }
                    else
                    {
                        if (inQuotedString)
                        {
                            temp += " " + s;
                        }
                        else
                        {
                            merged.Add(s);
                        }
                    }
                }

                // Final command line string array
                string[] curCmdLine = new string[merged.Count];
                int i = 0;
                foreach (string s in merged)
                {
                    curCmdLine[i++] = s;
                }

                string theCommand = curCmdLine[0].ToUpper();
                // Parse command and execute
                switch (theCommand)
                {
                    case "TESTINVENTORY":
                        _Client.Inventory.GetRootFolder().RequestDownloadContents(true, true, false);
                        break;
                    case "QUIT":
                    case "EXIT":
                    case "BYE":
                    case "Q":
                        shutdown = true;
                        break;

                    case "HELP":
                        if (curCmdLine.Length == 1)
                        {
                            Console.WriteLine();
                            Console.WriteLine(HorzLine);
                            Console.WriteLine("Available Commands:");
                            Console.WriteLine(HorzLine);

                            string CmdNameFormatString = "{0,-" + CommandMaxLen + "} : ";

                            foreach (Command cmd in CommandList)
                            {
                                Console.Write(CmdNameFormatString, cmd.GetCommandStrings()[0].ToUpper());
                                Console.WriteLine(cmd.GetSummaryText());
                            }

                            Console.Write(CmdNameFormatString, "QUIT");
                            Console.WriteLine("Exit Glider");

                            Console.WriteLine(HorzLine);
                            Console.WriteLine("To display help for a specific command, specify > HELP NAME_OF_COMMAND");
                        }
                        else
                        {
                            string HelpFor = curCmdLine[1].ToUpper();
                            if (CommandDictionary.ContainsKey(HelpFor))
                            {
                                Command cmd = CommandDictionary[HelpFor];
                                Console.WriteLine();
                                Console.Write(HelpFor + " : ");
                                Console.WriteLine(cmd.GetHelpText());
                            }
                            else
                            {
                                Console.WriteLine("Unknown Command: " + HelpFor);
                                Console.WriteLine("Type HELP for a list of available commands.");
                            }
                        }
                        break;

                    default:

                        if( CommandDictionary.ContainsKey(theCommand) )
                        {
                            Command cmd = CommandDictionary[theCommand];
                            Console.Write(cmd.HandleInput(curCmdLine));
                        } else {
                            Console.WriteLine("Unknown command '" + theCommand + "'.");
                            Console.WriteLine("Type HELP for a list of available commands.");
                        }
                        break;
                }

                Console.WriteLine();
            } while (shutdown == false);

        }

        /// <summary>
        /// Loads commands
        /// </summary>
        /// <returns>Any error messages</returns>
        protected List<string> LoadCommands( string AssemblyDLL, TextWriter LogWriter )
        {
            LogWriter.WriteLine("Loading Commands from: " + AssemblyDLL);

            List<string> Errors = new List<string>();

            // List all the types in the assembly that is passed in as a parameter
            Assembly LoadedAssembly = Assembly.LoadFrom(AssemblyDLL);

            // Walk through each Type and list their Information
            foreach (Type type in LoadedAssembly.GetTypes())
            {
                if (type.GetInterface("Feathertail.Command") != null )
                {
                    LogWriter.WriteLine("Loading " + type.FullName + "...");

                    Command cmd = (Command)Activator.CreateInstance(type);

                    foreach (string cmdString in cmd.GetCommandStrings())
                    {
                        if (CommandDictionary.ContainsKey(cmdString))
                        {
                            Errors.Add("A command string of " + cmdString + " already exists for " + CommandDictionary[cmdString].GetType().FullName + " not adding one for " + type.FullName);
                        }
                        else
                        {
                            // Add to command dictionary
                            CommandDictionary.Add(cmdString.ToUpper(), cmd);

                            // Update max command IncomingName length, used for formatting help output
                            if (cmdString.Length > CommandMaxLen)
                            {
                                CommandMaxLen = (byte)cmdString.Length;
                            }

                            // Add to Command List and Initialize
                            if (!CommandList.Contains(cmd))
                            {
                                CommandList.Add(cmd);

                                cmd.Setup(_Client);
                            }
                        }
                    }
                }
            }

            return Errors;

        }

        #region Client Connection/Disconnection

        /// <summary>
        /// Set the connected signal, once we're connected.
        /// </summary>
        /// <param IncomingName="sender"></param>
        protected void Network_OnConnected(object sender)
        {
            _ConnectedSignal.Set();

            _Client.Self.Status.FinishAnim = true;
            _Client.Self.Status.StandUp = true;

            _Client.Self.Status.SendUpdate();
            _Client.Self.Status.SendUpdate();

            _Client.Self.Status.FinishAnim = false;
            _Client.Self.Status.StandUp = false;

            _Client.Self.Status.UpdateTimer.Start();

            _Client.Appearance.BeginAgentSendAppearance();
        }

        /// <summary>
        /// Attempt to connect to SL
        /// </summary>
        /// <param IncomingName="FirstName"></param>
        /// <param IncomingName="LastName"></param>
        /// <param IncomingName="Password"></param>
        /// <returns></returns>
        protected bool Connect(string FirstName, string LastName, string Password)
        {
            Console.WriteLine("Attempting to connect and login to SecondLife.");

            // Setup Login to Second Life
            Dictionary<string, object> loginReply = new Dictionary<string, object>();

            // Login
            if (!_Client.Network.Login(FirstName, LastName, Password, "GliderCLI", "static.sprocket@gmail.com"))
            {
                // Login failed
                Console.WriteLine("Error logging in: " + _Client.Network.LoginMessage + " : " + _Client.Network.LoginErrorKey);
                return false;
            }

            // Login was successful
            Console.WriteLine("Login was successful.");
            Console.WriteLine("AgentID:   " + _Client.Network.AgentID);
            Console.WriteLine("SessionID: " + _Client.Network.SessionID);

            return true;
        }

        /// <summary>
        /// Attempt to disconnect from SL
        /// </summary>
        protected void Disconnect()
        {
            // Logout of Second Life
            Console.WriteLine("Request logout");
            _Client.Network.Logout();

            // Just a hack until we have a better way of waiting for logout, like a ManualEvent
            Thread.Sleep(3000);
        }


        #endregion

    }
}
